package com.hmall.search.service.impl;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hmall.common.client.ItemClient;
import com.hmall.common.constants.MQConstants;
import com.hmall.common.dto.Item;
import com.hmall.common.dto.PageDTO;
import com.hmall.search.pojo.RequestParams;
import com.hmall.search.pojo.itemDoc;
import com.hmall.search.service.SearchService;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements SearchService {


    //定义全局静态产量索引库名“hotel”
    private static final String INDDX_NAME = "item";
    //注入RestHighLevelClient对象
    @Autowired
    private RestHighLevelClient client;
    //定义ObjectMapper用于json与java对象转换
    @Autowired
    private ObjectMapper objectMapper;

    //远程调用公共方法
    @Autowired
    private ItemClient itemClient;


    /**
     * 酒店搜索自动补全
     */
    @Override
    public List<String> suggestion(String key) {
        //获取请求
        SearchRequest request = new SearchRequest(INDDX_NAME);

        try {
            //编写请求
            request.source().suggest(new SuggestBuilder()
                    .addSuggestion("itemSuggestion",
                            SuggestBuilders
                                    .completionSuggestion("suggestion")// field部分
                                    .prefix(key)  //text部分
                                    .skipDuplicates(true)  //skip_duplicates部分
                                    .size(10)  //size部分
                    ));
            //提交
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //获取itemSuggestion里面的数据
            CompletionSuggestion completionSuggestion = response.getSuggest().getSuggestion("itemSuggestion");

            //获取获取itemSuggestion里面的Options的数据
            List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();

            //空集合，存数据返回给前端
            List<String> result = new ArrayList<>();

            //遍历options 将数据存list集合里面
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                result.add(text);
            }

            return result;

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 酒店动态过滤条件,过滤项聚合功能
     *
     * @param params
     * @return 条件集合
     */
    @Override
    public Map<String, List<String>> filters(RequestParams params) {
        //获取搜索请求对象
        SearchRequest request = new SearchRequest(INDDX_NAME);

        try {
            //构建调教【这里集成了方法BoolQueryBuilder】
            BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(params);

            request.source().query(boolQueryBuilder);
            request.source().size(0);

            //添加聚合条件
            request.source().aggregation(AggregationBuilders.terms("categoryAgg").field("category").size(100));
            request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));

            //执行查询
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //空集合保存数据
            Map<String, List<String>> resultMap = new HashMap<>();

            //封装数据到List集合  这里也是调用了方法【KeyListFromAggName】
            List<String> cityAgg = getKeyListFromAggName(response, "categoryAgg");
            List<String> brandAgg = getKeyListFromAggName(response, "brandAgg");

            //封装数据到map集合
            resultMap.put("category", cityAgg);
            resultMap.put("brand", brandAgg);

            return resultMap;

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 【KeyListFromAggName】
     * 遍历数据添加到集合的方法。
     *
     * @param response 执行结果
     * @param aggName  agg字段的名字
     * @return 封装好的list集合对象
     */
    private List<String> getKeyListFromAggName(SearchResponse response, String aggName) {
        //获取getAggregations的消息 ，
        Terms terms = response.getAggregations().get(aggName);

        //获取Buckets的集合，查询到的所有数据
        List<? extends Terms.Bucket> buckets = terms.getBuckets();

        //创建空集合装数据
        List<String> listKey = new ArrayList<>();

        //遍历Buckets，单独获取key数据
        for (Terms.Bucket bucket : buckets) {
            //将数据转String
            String key = bucket.getKeyAsString();
            //保存到list集合
            listKey.add(key);
        }
        return listKey;
    }

    /**
     * 【BoolQueryBuilder】
     * 构建页面传入的搜索条件
     *
     * @param params
     * @return
     */
    private BoolQueryBuilder getBoolQueryBuilder(RequestParams params) {
        //拼接条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //3.判断查询条件key是否为空, 字符串非空判断StringUtils.isNotEmpty(字符串)
        if (StringUtils.isNotEmpty(params.getKey())) {
            //3.1 不为空全文检索"all"
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            //3.2 为空全文检索所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //判断条件 ： 分类
        if (StringUtils.isNotEmpty(params.getCategory())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category", params.getCategory()));
        }
        //判断条件 ： 名称
        if (StringUtils.isNotEmpty(params.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }

        return boolQueryBuilder;
    }


    /**
     * 酒店条件搜索  + 分页 + 广告
     * @param params 前端提供数据
     * @return
     */
    @Override
    public PageDTO<itemDoc> search(RequestParams params) {

        //1.判断查询页码 和 每页大小是否为null, 如果为null，设置默认值 page=1 和 size=20
        if (params.getPage() == null) params.setPage(1);
        if (params.getSize() == null) params.setSize(20);

        try {
            //2.创建搜索请求对象 SearchRequest
            SearchRequest request = new SearchRequest(INDDX_NAME);

            //拼接条件
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

            //3.判断查询条件key是否为空, 字符串非空判断StringUtils.isNotEmpty(字符串)
            if (StringUtils.isNotEmpty(params.getKey())) {
                //3.1 不为空全文检索"all"
                boolQueryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
            } else {
                //3.2 为空全文检索所有
                boolQueryBuilder.must(QueryBuilders.matchAllQuery());
            }
            //判断条件 ：  分类
            if (StringUtils.isNotEmpty(params.getCategory())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("category", params.getCategory()));
            }
            //判断条件 ： 品牌
            if (StringUtils.isNotEmpty(params.getBrand())) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
            }
            //判断条件 ： 价格区间
            if (params.getMinPrice() != null && params.getMaxPrice() != null) {
                boolQueryBuilder.filter(
                        QueryBuilders.rangeQuery("price")
                                .gte(params.getMinPrice() * 100) //大于等于
                                .lte(params.getMaxPrice() * 100)); //小于等于
            }


            //算分函数
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                    boolQueryBuilder,//原始条件
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                    QueryBuilders.termQuery("isAD", true),
                                    ScoreFunctionBuilders.weightFactorFunction(5)
                            )
                    }
            ).boostMode(CombineFunction.SUM);


           // request.source().query(boolQueryBuilder);
            request.source().query(functionScoreQueryBuilder);

            //4.计算分页起始位置，公式=（当前页-1）* 每页大小
            int from = (params.getPage() - 1) * params.getSize();
            //5.设置查询分页from和seize


            request.source().from(from).size(params.getSize());
            //6.设置高亮查询字段，使用HighlightBuilder对象构建高亮字段
            request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));


            //7.执行搜索，获取结果
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //8.封装结果并返回
            SearchHits searchHits = response.getHits();
            //9.获取总记录数
            long total = searchHits.getTotalHits().value;
            //定义集合List<HotelDoc>收集数据
            List<itemDoc> itemDocList = new ArrayList<>();
            //10.遍历获取文档集合数据
            for (SearchHit searchHit : searchHits) {
                //10.1 获取每个文档的json数据
                String json = searchHit.getSourceAsString();
                //10.2 json转换为HotelDoc对象
                itemDoc itemDoc = objectMapper.readValue(json, itemDoc.class);
                //10.3替换高亮内容
                HighlightField highlightField = searchHit.getHighlightFields().get("name");
                if (highlightField != null) {
                    itemDoc.setName(highlightField.getFragments()[0].toString());
                }

                //10.4 将hotelDoc对象添加到集合中
                itemDocList.add(itemDoc);
            }
            //11.封装分页数据返回
            PageDTO<itemDoc> pageDTO = new PageDTO<>(total, itemDocList);
            return pageDTO;
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * Rabbit数据同步
     * 添加 + 修改 数据更新
     * 根据id先查询，然后添加
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MQConstants.MALL_INSERT_QUEUE,durable = "true"),
            exchange = @Exchange(MQConstants.MALL_EXCHANGE),
            key = MQConstants.MALL_INSERT_KEY
    ))
    public void InsetById(Integer id){

        //远程调用方法
        Item item = itemClient.selectById(id);

        //4.根据item对象数据封装到itemDoc对象中
       itemDoc itemDoc=new itemDoc(item);

        try {
            //5.创建 IndexRequest 请求对象，并构建 DSL 添加文档命令
            //5.1 构建添加文档命令,格式为：POST item/_doc/id
            IndexRequest request=new IndexRequest(INDDX_NAME).id(item.getId().toString());

            //5.2 将itemDoc对象转换为json字符串
            String json = objectMapper.writeValueAsString(itemDoc);

            //5.3 继续构建添加文档命令填充文档数据，格式为：POST item/_doc/id{文档数据}
            request.source(json, XContentType.JSON);

            //6.执行添加文档命令并获取返回结果对象
            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
            //7.打印获取到返回的id
            System.out.println("修改成功："+response);

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }



    /**
     * 删除ES的文档数据
     *
     * @param id
     */

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MQConstants.MALL_DELETE_QUEUE,durable = "true"),
            exchange = @Exchange(value = MQConstants.MALL_EXCHANGE),
            key = MQConstants.MALL_DELETE_KEY
    ))
    public void deleteById(Long id){

        try {
            DeleteRequest deleteRequest=new DeleteRequest(INDDX_NAME).id(id.toString());
            //2.执行删除文档命令并且返回删除结果对象 DeleteResponse
            DeleteResponse delete = client.delete(deleteRequest, RequestOptions.DEFAULT);
            //3.打印 删除响应对象的 id 输出
            System.out.println("删除成功："+delete);

        } catch (IOException e) {
            e.printStackTrace();
            throw  new RuntimeException(e);
        }
    }



}
